﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;

namespace Scoreboard.Model
{
	public class RatingEvaluator
	{
		public const int RatingStartValue = 1500;

		private static readonly object _syncRoot = new object();

		public static void RecalculateRating()
		{
			EntitiesContainer container = new EntitiesContainer();

			foreach (var user in container.Users)
			{
				user.RatingGames = 0;
				user.RatingValue = RatingStartValue;
			}

			foreach (var game in container.Games.OrderBy(g => g.Date))
			{
				ApplyOnRating(game);
			}

			container.SaveChanges();
		}

		public static void ApplyOnRating(Game game)
		{	
			lock (_syncRoot)
			{
				if (game.Participant.Mate != null)
				{				

					int participantRating = (game.Participant.Player.RatingValue +
						game.Participant.Mate.RatingValue) / 2;
					int opponentRating = (game.Opponent.Player.RatingValue +
						game.Opponent.Mate.RatingValue) / 2;

					int t1R = participantRating;
					int t2R = opponentRating;

					int t1p1N = game.Participant.Player.RatingGames;
					int t1p2N = game.Participant.Mate.RatingGames;

					int t2p1N = game.Opponent.Player.RatingGames;
					int t2p2N = game.Opponent.Mate.RatingGames;

					int t1N = (t1p1N + t1p2N) / 2;
					int t2N = (t2p1N + t2p2N) / 2;

					EvaluateRatings(game.ParticipantScore, game.OpponentScore, t1N, t2N, ref t1R, ref t2R);

					int t1Gain = t1R - participantRating;
					int t2Gain = t2R - opponentRating;

					game.Participant.Player.RatingValue += t1Gain;
					game.Participant.Mate.RatingValue += t1Gain;

					game.Opponent.Player.RatingValue += t2Gain;
					game.Opponent.Mate.RatingValue += t2Gain;

					game.Participant.Player.RatingGames++;
					game.Participant.Mate.RatingGames++;

					game.Opponent.Player.RatingGames++;
					game.Opponent.Mate.RatingGames++;
					
				}
				else
				{
					

					int p1N = game.Participant.Player.RatingGames;
					
					int p2N = game.Opponent.Player.RatingGames;
					
					int p1R = game.Participant.Player.RatingValue;
					int p2R = game.Opponent.Player.RatingValue;
					
					EvaluateRatings(game.ParticipantScore, game.OpponentScore, p1N, p2N, ref p1R, ref p2R);
									

					game.Participant.Player.RatingValue = p1R;
					game.Opponent.Player.RatingValue = p2R;
					
					game.Participant.Player.RatingGames++;
					game.Opponent.Player.RatingGames++;

					//Debug.Print("Singles: {0} ({1}) - ({2}) {3} : {4} {5}", game.Participant.Player.FriendlyName, game.ParticipantScore, game.OpponentScore, game.Opponent.Player.FriendlyName, game.Participant.Player.RatingValue, game.Opponent.Player.RatingValue);
				}
			}
		}

		private static void EvaluateRatings(
			double plOneScore,
			double plTwoScore,
			int p1N,
			int p2N,
			ref int p1R,
			ref int p2R)
		{
			double p1S;
			double p2S;

			double p1Nr;
			double p2Nr;

			double p1Ne;
			double p2Ne;

			double p1K;
			double p2K;

			double p1E;
			double p2E;

			// --------- S ----------

			if (plOneScore > plTwoScore)
			{
				p1S = 1.0;
				p2S = 0;
			}
			else
			{
				p1S = 0;
				p2S = 1.0;
			}

			// --------- Nr ----------

			p1Nr = GetNr(p1R);

			p2Nr = GetNr(p2R);

			// ------------ Ne ------------

			p1Ne = Math.Min(p1N, p1Nr);
			p2Ne = Math.Min(p2N, p2Nr);

			// ----------- K --------------

			p1K = GetK(p1Ne);
			p2K = GetK(p2Ne);

			// ----------- E --------------

			p1E = GetE(p1R, p2R);
			p2E = GetE(p2R, p1R);

			// ----------- R ------------

			p1R = GetNewR(p1R, p1K, p1S, p1E);
			p2R = GetNewR(p2R, p2K, p2S, p2E);
		}

		private static double GetNr(double R)
		{
			if (R > 2200)
			{
				return 50;
			}

			double result = 50.0 / Math.Sqrt(1.0 + Math.Pow(2200.0 - R, 2.0) / 100000.0);

			return result;
		}

		private static double GetK(double Ne)
		{
			double result = 800.0 / (Ne + 1.0);

			return result;
		}

		private static double GetE(double ownR, double oppR)
		{
			double result = 1 / (Math.Pow(10.0, -(ownR - oppR) / 400.0) + 1.0);

			return result;
		}

		private static int GetNewR(double oldR, double K, double S, double E)
		{
			double result = oldR + K * (S - E);

			int integerResult = (int)Math.Round(result);

			return integerResult;
		}
	}
}
